package org.zjvis.datascience.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.zjvis.datascience.common.dto.JlabDTO;
import org.zjvis.datascience.common.dto.ProjectDTO;
import org.zjvis.datascience.common.dto.TaskDTO;
import org.zjvis.datascience.common.dto.TaskInstanceDTO;
import org.zjvis.datascience.common.enums.ExeFlagEnum;
import org.zjvis.datascience.common.util.TaskUtil;
import org.zjvis.datascience.common.vo.TaskVO;
import org.zjvis.datascience.service.dag.TaskRunnerResult;
import org.zjvis.datascience.service.mapper.JlabMapper;
import org.zjvis.datascience.service.mapper.ProjectMapper;
import org.zjvis.datascience.service.mapper.TaskInstanceMapper;
import org.zjvis.datascience.service.mapper.TaskMapper;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/**
 * @description JLab JupyterNotebook Service
 * @date 2021-12-15
 */
@Service
public class JlabService {

    private final static Logger logger = LoggerFactory.getLogger("JLAB");

    @Autowired
    private JlabMapper JlabMapper;

    @Autowired
    private TaskMapper taskMapper;

    @Autowired
    private TaskInstanceMapper taskInstanceMapper;

    @Autowired
    private ProjectMapper projectMapper;

    @Value("${Jlab.initLab}")
    private String initLab;

    @Value("${Jlab.homeDir}")
    private String homeDir;

    @Value("${Jlab.notebookDir}")
    private String notebookDir;
//    @Value("${Jlab.sshConn}")
//    private String sshConn;

    @Value("${Jlab.getToken}")
    private String getToken;

    @Value("${Jlab.loadData}")
    private String loadData;

    @Value("${Jlab.loadView}")
    private String loadView;

    @Value("${Jlab.df2db}")
    private String df2db;

    @Value("${Jlab.urlTemp}")
    private String urlTemp;

    @Value("${Jlab.userName}")
    private String userName;

    @Value("${Jlab.host}")
    private String host;

    @Value("${Jlab.password}")
    private String password;

    @Value("${Jlab.port}")
    private int port;

//    @Value("${Jlab.urlPrefix}")
//    private String urlPrefix;

    private final static Logger log = LoggerFactory.getLogger("JlabService");


    private boolean isContainKey(String name, String searchKey) {
        if (searchKey == null) {
            return true;
        }
        String lowerName = name.toLowerCase();
        String lowerKey = searchKey.toLowerCase();
        return lowerName.contains(lowerKey);
    }

    public Long queryExistence(Long user_id) {
        return JlabMapper.queryExistence(user_id);
    }

    public Long insert(JlabDTO dto) {
        return JlabMapper.insert(dto);
    }

    public Long update(JlabDTO dto) {
        return JlabMapper.update(dto);
    }

    public Long updateByUser(JlabDTO dto) {
        return JlabMapper.updateByUser(dto);
    }

    public JlabDTO queryByUserId(Long userId) {
        return JlabMapper.queryByUserId(userId);
    }

    public void renameTaskDir(TaskVO vo, String oldDirName) {
//        TaskDTO task = taskService.queryById(vo.getId());
//        String oldDirName = task.getName();
        String projectName = projectMapper.selectByPrimaryKey(vo.getProjectId()).getName() + "/";
        String newDirName = vo.getName();
        //"ssh " + sshConn +
        String cmd = "mv " + notebookDir
                + "user_" + vo.getUserId() + "/" + legalize(projectName + oldDirName) + " " + notebookDir
                + "user_" + vo.getUserId() + "/" + legalize(projectName + newDirName);
        execCmd(cmd);
    }

    public void renameProjectDir(ProjectDTO projectDTO, String newDirName) {
//        TaskDTO task = taskService.queryById(vo.getId());
//        String oldDirName = task.getName();
        //String projectName = projectService.query(vo.getProjectId()).getName() + "/";
        String oldDirName = projectDTO.getName();
        //"ssh " + sshConn +
        String cmd = "mv " + notebookDir
                + "user_" + projectDTO.getGmtCreator() + "/" + legalize(oldDirName) + " " + notebookDir
                + "user_" + projectDTO.getGmtCreator() + "/" + legalize(newDirName);
        execCmd(cmd);
    }

    public void copyPaste(TaskDTO vo, String newDirName) {
//        TaskDTO task = taskService.queryById(vo.getId());
//        String oldDirName = task.getName();
        String projectName = projectMapper.selectByPrimaryKey(vo.getProjectId()).getName() + "/";
        String oldDirName = vo.getName();
        if (!newDirName.equals(oldDirName)) {
            //"ssh " + sshConn +
            String cmd = "cp -r " + notebookDir
                    + "user_" + vo.getUserId() + "/" + legalize(projectName + oldDirName) + " " + notebookDir
                    + "user_" + vo.getUserId() + "/" + legalize(projectName + newDirName);
            execCmd(cmd);
        }
    }

    public void copyPasteProjectDir(Long userId, String oldProjectName, String newProjectName) {
        //"ssh " + sshConn +
        String cmd = "cp -r " + notebookDir
                + "user_" + userId + "/" + legalize(oldProjectName) + " " + notebookDir
                + "user_" + userId + "/" + legalize(newProjectName);
        execCmd(cmd);
    }

    public void start(JlabDTO jlab) {
        Long userId = jlab.getUserId();
        //"ssh " + sshConn +
        String cmd = "docker start user_" + userId;
        execCmd(cmd);
        execFinal(userId, jlab.getPort(), ExeFlagEnum.GET_TOKEN.getVal()
                , null, null, " ");
    }

//    private void execCmd2(String cmd) {
//        int code = 0;
//        Process p;
//        try {
//            String[] enp = new String[]{""};
//            /**
//             * Note: [install 'sshpass'] for local deployment
//             */
//            cmd = "sshpass -p zjvis123 " + cmd;
//
//            p = Runtime.getRuntime().exec(cmd, enp);
//            code = p.waitFor();
//            if (code != 0) {
//                String result = IOUtils.toString(p.getErrorStream(), "utf-8");
//                log.error("Command execute Error! reason = " + result);
//            }
//        } catch (Exception e) {
//            log.error("Exception! " + e.getMessage());
//        }
//    }

    private void execCmd(String cmd) {
        JSch jSch = new JSch();
        Session session = null;
        ChannelExec channelExec = null;
        BufferedReader inputStreamReader = null;
        try {
            // 1. 获取 ssh session

            session = jSch.getSession(userName, host, port);
            session.setPassword(password);
//            session.setTimeout(timeout);
            session.setConfig("StrictHostKeyChecking", "no");
            session.connect();  // 获取到 ssh session

            // 2. 通过 exec 方式执行 shell 命令
            channelExec = (ChannelExec) session.openChannel("exec");
            channelExec.setCommand(cmd);
            channelExec.connect();  // 执行命令

            // 3. 获取标准输入流
            //inputStreamReader = new BufferedReader(new InputStreamReader(channelExec.getInputStream()));
            // 4. 获取标准错误输入流
            //errInputStreamReader = new BufferedReader(new InputStreamReader(channelExec.getErrStream()));
        } catch (Exception e) {
            log.error("Exception! " + e.getMessage());
        } finally {
            try {
                if (inputStreamReader != null) {
                    inputStreamReader.close();
                }
                if (channelExec != null) {
                    channelExec.disconnect();
                }
                if (session != null) {
                    session.disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void load(TaskVO vo) {
        TaskDTO task = taskMapper.queryById(vo.getId());
        String taskDirName = getFolderName(task);
        String projectDirName = legalize(projectMapper.selectByPrimaryKey(task.getProjectId()).getName());
        //ssh " + sshConn +
        String cmd =  "mkdir -p " + notebookDir
                + "user_" + task.getUserId() + "/" + projectDirName + " \n mkdir -p " + notebookDir
                + "user_" + task.getUserId() + "/" + taskDirName;
        execCmd(cmd);
    }

    public String queryURL(TaskVO vo) {
        Long userId = vo.getUserId();
        JlabDTO jlab = this.queryByUserId(userId);

        if (jlab.getActive() != 1) {
            this.start(jlab);
        }

        String extra = getFolderName(taskMapper.queryById(vo.getId()));
        if (jlab != null && jlab.getPort() != null) {
            return String.format(urlTemp, jlab.getPort(), extra, jlab.getToken());
        } else {
            return this.execFinal(userId, jlab.getPort(), ExeFlagEnum.GET_TOKEN.getVal()
                    , null, null, extra);
        }
    }

    private String legalize(String dirName) {
        return dirName.replace(")", "")
                .replace("(", "_")
                .replace(" ", "_");
    }

    private String getFolderName(TaskDTO task) {
        String projectName = projectMapper.selectByPrimaryKey(task.getProjectId()).getName();
        String folderName = projectName + "/" + task.getName();
        //folderName = legalize(folderName)
        return legalize(folderName);
    }

    private String getFileName(TaskDTO task) {
        JSONObject data_json = JSONObject.parseObject(task.getDataJson());
        JSONArray setParamsJsonArray = data_json.getJSONArray("setParams");
        //List<String> columnsFilterd = ToolUtil.filterTypeAndCol(inputCols, columnTypes, filterType).getKey();
        JSONObject setParams = setParamsJsonArray.getJSONObject(0);
        JSONObject formData = setParams.getJSONObject("formData");
        String filenameFormData = formData.getString("filename");
        return legalize(filenameFormData);
    }

    public String loadJlabls(TaskVO vo) {
        Long taskId = vo.getId();
        TaskDTO task = taskMapper.queryById(taskId);
        //"ssh " + sshConn +
        String cmd = "ls -ltr " + notebookDir
                + "user_" + task.getUserId() + "/" + getFolderName(task);
        List<String> fileNames = new ArrayList<>();

//        int code = 0;
//        Process p;
//        try {
//            String[] enp = new String[]{""};
//
//            /**
//             * Note: [install 'sshpass'] for local deployment
//             */
//            cmd = "sshpass -p zjvis123 " + cmd;
//
//            p = Runtime.getRuntime().exec(cmd, enp);
//            InputStream inputStream = p.getInputStream();
//            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//            String line;
//            while ((line = bufferedReader.readLine()) != null) {
//                if (isContainKey(line, ".csv")) {
//                    String[] lineSplit = line.split(" ");
//                    fileNames.add(lineSplit[lineSplit.length - 1]);
//                }
//            }
//            code = p.waitFor();
//            if (code != 0) {
//                String result = IOUtils.toString(p.getErrorStream(), "utf-8");
//                log.error("Command execute Error! reason = " + result);
//            }
//        } catch (Exception e) {
//            log.error("Exception! " + e.getMessage());
//            return "Error in ls";
//        }

        JSch jSch = new JSch();
        Session session = null;
        ChannelExec channelExec = null;
        BufferedReader inputStreamReader = null;
        try {
            // 1. 获取 ssh session
            session = jSch.getSession(userName, host, port);
            session.setPassword(password);
//            session.setTimeout(timeout);
            session.setConfig("StrictHostKeyChecking", "no");
            session.connect();  // 获取到 ssh session

            // 2. 通过 exec 方式执行 shell 命令
            channelExec = (ChannelExec) session.openChannel("exec");
            channelExec.setCommand(cmd);
            channelExec.connect();  // 执行命令

            // 3. 获取标准输入流
            inputStreamReader = new BufferedReader(new InputStreamReader(channelExec.getInputStream()));
            String line;
            while ((line = inputStreamReader.readLine()) != null) {
                if (isContainKey(line, ".csv")) {
                    String[] lineSplit = line.split(" ");
                    String fileName = lineSplit[lineSplit.length - 1];
                    byte[] tmpByte = fileName.getBytes(Charset.forName("GBK"));
                    fileNames.add(new String(tmpByte, Charset.forName("GBK")));
                }
            }
            // 4. 获取标准错误输入流
            //errInputStreamReader = new BufferedReader(new InputStreamReader(channelExec.getErrStream()));
        } catch (Exception e) {
            log.error("Exception! " + e.getMessage());
        } finally {
            try {
                if (inputStreamReader != null) {
                    inputStreamReader.close();
                }
                if (channelExec != null) {
                    channelExec.disconnect();
                }
                if (session != null) {
                    session.disconnect();
                }
            } catch (IOException e) {
                log.error("Exception finally! " + e.getMessage());
            }
        }

        //load setParams
        JSONObject data_json = JSONObject.parseObject(task.getDataJson());
        JSONArray setParamsJsonArray = data_json.getJSONArray("setParams");

        //List<String> columnsFilterd = ToolUtil.filterTypeAndCol(inputCols, columnTypes, filterType).getKey();
        JSONObject setParams = setParamsJsonArray.getJSONObject(0);
        JSONArray fileArray = new JSONArray();

        for (String file : fileNames) {
            JSONObject optionPair = new JSONObject();
            optionPair.put("label", file);
            optionPair.put("value", file);
            fileArray.add(optionPair);
        }
        setParams.getJSONArray("formItem").getJSONObject(0)
                .getJSONObject("props").put("options", fileArray);

        JSONObject formData = setParams.getJSONObject("formData");
        String filenameFormData = formData.getString("filename");
        if (filenameFormData != null) {
            if (filenameFormData.equals("<暂无可选csv文件>")) {
                if (fileArray.size() != 0) {
                    formData.put("filename", fileNames.get(fileNames.size() - 1).toString());
                } else {
                    formData.put("filename", "<请确保当前节点目录下有csv文件>");
                }
            } else {
                if (!fileNames.contains(filenameFormData)) {
                    formData.put("filename", "<请选择csv文件>");
                }
            }
        }

        task.setDataJson(data_json.toString());
        taskMapper.update(task);
        return fileArray.toString();
    }

    public TaskRunnerResult execRunner(TaskInstanceDTO instance) {
        TaskRunnerResult taskRunnerResult = new TaskRunnerResult();
        JlabDTO jlab = this.queryByUserId(instance.getUserId());
        TaskDTO task = taskMapper.queryById(instance.getTaskId());
        String folderName = getFolderName(task);
        String fileName = getFileName(task);
        String file_path = notebookDir + "user_" + task.getUserId()
                + "/" + folderName + "/" + fileName;
        String table_name = "pipeline.jlab_" + instance.getTaskId() + System.currentTimeMillis();
        ;

        //actual run
        String extra = file_path + " " + table_name + " " + instance.getId();
        String res = execFinal(instance.getUserId(), jlab.getId(), ExeFlagEnum.UPDATE_TASK.getVal()
                , null, null, extra);
//        try {
//            Thread.sleep(1000);
//        }catch (InterruptedException ex){
//            Thread.currentThread().interrupt();
//        }

        TaskInstanceDTO instanceNew = taskInstanceMapper.queryById(instance.getId());
        JSONObject logInfo = new JSONObject();
        if (res.equals("finished")) {
            instance.setDataJson(instanceNew.getDataJson());
            taskRunnerResult.setStatus(0);
            //instance.setStatus("SUCCESS");

            logInfo.put("result", "SUCCESS");
            instance.setLogInfo(logInfo.toString());
//            taskRunnerResult.setOutput();
        } else {
            taskRunnerResult.setStatus(500);
            //instance.setStatus("FAIL");
            logInfo.put("result", "res");
            instance.setLogInfo(logInfo.toString());
//            taskRunnerResult.setOutput("FAIL");
        }
        return taskRunnerResult;
    }

    public String exec(Long userId, Long labId, int flag) {
        return execFinal(userId, labId, flag, null, null, null);
    }

    public String execApp(Long userId, Long labId, int flag, TaskDTO parentNode, TaskDTO childNode) {
        return execFinal(userId, labId, flag, parentNode, childNode, null);
    }

//    public Long getIdlePort(Long port){
//        try {
//            ServerSocket socket = new ServerSocket(Math.toIntExact(port));
//            socket.close();
//        } catch (IOException ex) {
//            port += 1;
//            port = getIdlePort(port);
//        }
//        return port;
//    }

    private static int findFreePort() throws IOException {
        try (ServerSocket serverSocket = new ServerSocket(0)) {
            int localPort = serverSocket.getLocalPort();
            if (localPort <= 0) {
                throw new IOException(String.format(
                        "get local port failed: result of localPort=%s <= 0", localPort
                ));
            }
            return localPort;
        }
    }

    public String execFinal(Long userId, Long labId, int flag, TaskDTO parentNode, TaskDTO childNode, String extra) {
        JlabDTO jlab = new JlabDTO();
        jlab.setUserId(userId);

        Process p;

        //default = get token
        //"ssh " + sshConn +
        String cmd = "sh " + homeDir + getToken + " " + userId;


        if (flag == ExeFlagEnum.INIT_NEW_LAB.getVal()) {
            //todo port
            //Long port = labId + 9006;
            Long newport = null;
            try {
                newport = Long.valueOf(findFreePort());
            } catch (IOException e) {
                logger.error("Failed to find free port with error: " + e);
                newport = labId + 9006;
            }
            cmd = "sh " + homeDir + initLab + " " + userId + " " + port;
            jlab.setPort(newport);
            this.updateByUser(jlab);
        } else if (flag == ExeFlagEnum.LOAD_DATA.getVal()) {
            if (parentNode != null && childNode != null) {
                String tableName = TaskUtil.extractTableStr(parentNode);
//                String projectName = projectService.query(parentNode.getProjectId()).getName();
//                String folderName = projectName + "/" + childNode.getName();

                //load tableName from GP to [parentNode.Name].csv
                String appendix = " " + tableName + " " + getFolderName(childNode) + " " + legalize(parentNode.getName());
//                appendix = appendix.replace(")","").replace("(","_");
                if (!isContainKey(tableName, "view")) {
                    //"ssh " + sshConn +
                    cmd = "sh " + homeDir + loadData + " " + userId + appendix;
                } else {
                    //"ssh " + sshConn +
                    cmd = "sh " + homeDir + loadView + " " + userId + appendix;
                }
            }
        } else if (flag == ExeFlagEnum.UPDATE_TASK.getVal()) {
            //"ssh " + sshConn +
            cmd = "sh " + homeDir + df2db + " " + extra;
        }
//        int code = 0;
//        try {
//            String[] enp = new String[]{""};
//
//            /**
//             * Note: [install 'sshpass'] for local deployment
//             */
//            cmd = "sshpass -p zjvis123 " + cmd;
//            //cmd = cmd.replace(")", "").replace("(", "_");
//            p = Runtime.getRuntime().exec(cmd, enp);
//            if (flag == ExeFlagEnum.INIT_NEW_LAB.getVal() || flag == ExeFlagEnum.GET_TOKEN.getVal()) {
//                InputStream inputStream = p.getInputStream();
//                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//                String line;
//                while ((line = bufferedReader.readLine()) != null) {
//                    if (isContainKey(line, "token")) {
//                        String token = line.split(" ::")[0].split("=")[1];
//                        jlab.setActive(1);
//                        jlab.setToken(token);
//                        this.updateByUser(jlab);
//                    }
//                }
//            }
//            code = p.waitFor();
//            if (code != 0) {
//                String result = IOUtils.toString(p.getErrorStream(), "utf-8");
//                log.error("Command execute Error! reason = " + result);
//            }
//        } catch (Exception e) {
//            log.error("Exception! " + e.getMessage());
//            return e.getMessage();
//        }

        JSch jSch = new JSch();
        Session session = null;
        ChannelExec channelExec = null;
        BufferedReader inputStreamReader = null;
        try {
            // 1. 获取 ssh session

            session = jSch.getSession(userName, host, port);
            session.setPassword(password);
//            session.setTimeout(timeout);
            session.setConfig("StrictHostKeyChecking", "no");
            session.connect();  // 获取到 ssh session

            // 2. 通过 exec 方式执行 shell 命令
            channelExec = (ChannelExec) session.openChannel("exec");
            cmd = "cd " + homeDir + "\n" + cmd;
            channelExec.setCommand(cmd);
            channelExec.connect();  // 执行命令

            // 3. 获取标准输入流
            if (flag == ExeFlagEnum.INIT_NEW_LAB.getVal() || flag == ExeFlagEnum.GET_TOKEN.getVal()) {
                inputStreamReader = new BufferedReader(new InputStreamReader(channelExec.getInputStream()));
                String line;
                while ((line = inputStreamReader.readLine()) != null) {
                    if (isContainKey(line, "token")) {
                        String token = line.split(" ::")[0].split("=")[1];
                        jlab.setActive(1);
                        jlab.setToken(token);
                        this.updateByUser(jlab);
                    }
                }
            }
            if (flag == ExeFlagEnum.UPDATE_TASK.getVal()){
                inputStreamReader = new BufferedReader(new InputStreamReader(channelExec.getInputStream()));
                String line;
                while ((line = inputStreamReader.readLine()) != null) {
                    logger.info(line);
                }
            }
            // 4. 获取标准错误输入流
            //errInputStreamReader = new BufferedReader(new InputStreamReader(channelExec.getErrStream()));
        } catch (Exception e) {
            log.error("Exception! " + e.getMessage());
        } finally {
            try {
                if (inputStreamReader != null) {
                    inputStreamReader.close();
                }
                if (channelExec != null) {
                    channelExec.disconnect();
                }
                if (session != null) {
                    session.disconnect();
                }
            } catch (IOException e) {
                log.error("Exception finally! " + e.getMessage());
            }
        }
        if (flag == ExeFlagEnum.GET_TOKEN.getVal()) {
            return String.format(urlTemp, labId, extra, jlab.getToken());
        }
        return "finished";
    }
}
